package org.durandj.ystream.client;

import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.swing.JOptionPane;

import org.durandj.ystream.client.message.InvalidMessageContentsException;
import org.durandj.ystream.client.message.Message;
import org.durandj.ystream.client.message.MessageHandler;
import org.durandj.ystream.client.message.MessageID;
import org.durandj.ystream.client.message.Message.Priority;
import org.durandj.ystream.client.network.NetworkDaemon;
import org.durandj.ystream.client.network.Server;
import org.durandj.ystream.client.ui.VLCDialog;
import org.durandj.ystream.client.ui.YStreamFrame;
import org.durandj.ystream.client.util.ByteBufferUtils;
import org.durandj.ystream.client.util.Settings;
import org.durandj.ystream.client.util.Timer;

/**
 * The main class of the YStream application. This is the class that starts the application and
 * does all the heavy lifting and communications work.
 * 
 * @author durandj
 * @version 1.0
 *
 */
public class YStreamClient extends MessageHandler
{
	// TODO: Each thread should have an "oops" catch that safely shuts down the application.
	// TODO: The output stream should be redirected to a file once the jar has been made.
	/**
	 * Main startup method. As of now there are no usable parameters. You can still supply
	 * them if it makes you feel special =P
	 * 
	 * @param args - not used
	 */
	public static void main(String[] args) throws Exception
	{
		singleton = new YStreamClient();
		singleton.run();
	}
	
	/**
	 * The one instance of the YStream client.
	 */
	private static YStreamClient singleton;
	
	/**
	 * Gets a reference to the YStream client.
	 * 
	 * @return YStream client reference.
	 */
	public static YStreamClient getClient()
	{
		return singleton;
	}
	
	/**
	 * The version of YStream client.
	 */
	public static final String VERSION = "0.0.5";
	
	/**
	 * The flag telling the client to keep running.
	 */
	private Boolean alive;
	
	/**
	 * The music player.
	 */
	private YStreamPlayer player;
	
	/**
	 * The GUI of the application.
	 */
	private YStreamFrame window;
	
	/**
	 * The list of client event listeners.
	 */
	private List<YStreamClientListener> listeners;
	
	/**
	 * The server connection.
	 */
	// TODO: I don't think this needs to be saved here...
	private Server server;
	
	/**
	 * The network daemon.
	 */
	private NetworkDaemon networkDaemon;
	
	/**
	 * The streaming flag.
	 */
	private boolean isStreaming;
	
	/**
	 * Create an instance of the YStream client application. This is made private because there realy should only be
	 * one instance of the client.
	 */
	private YStreamClient()
	{
		super();
		
		alive = true;
		
		// Force the settings to load.
		Settings.getSettings();
	}
	
	/**
	 * Checks that the currently stored VLC path is valid.
	 * 
	 * @param path - the current VLC path
	 * @return is it valid?
	 */
	private boolean verifyVLC(String path)
	{
		// Check the string.
		if(path == null || path.isEmpty())
			return false;
		
		// Check if the path is valid and if it is a directory.
		File f = new File(path);
		if(!f.exists() || !f.isDirectory())
			return false;
		
		// Check if the directory contains VLC.
		File files[] = f.listFiles();
		for(File c : files)
			if(c.getName().equals("libvlc.dll"))
				return true;
		
		return false;
	}
	
	/**
	 * Runs the application and handles client messages.
	 */
	private void run()
	{
		listeners = new LinkedList<YStreamClientListener>();
	
		// We need to do some validation of the settings and start up so all messages are
		// reported to the logger.
		log.info("Client is starting...");
		
		log.info("Starting GUI...");
		window = new YStreamFrame();
		window.setVisible(true);
		log.info("GUI started!");
		
		log.info("Subscribing to GUI messages...");
		window.subscribe(this);
		log.info("Subscribed to GUI messages!");
		
		log.info("Checking VLC settings...");
		// Check if there's a setting for VLCJ yet and if not lets make one!
		String vlcpath = Settings.getSettings().getSetting("vlc_path");
		if(!verifyVLC(vlcpath))
		{
			log.info("The VLC path is currently unknown. Requesting path from user...");
			
			VLCDialog dlg = new VLCDialog(window);
			dlg.setVisible(true);
			
			// The user has chosen to exit instead...
			if(dlg.getResult() == VLCDialog.RESULT_CANCEL)
			{
				log.warning("No VLC was specified so YStream is closing.");
				
				JOptionPane.showMessageDialog(window,
				                              "YStream cannot run without VLC so YStream must now exit.",
				                              "No VLC",
				                              JOptionPane.INFORMATION_MESSAGE);
				
				window.dispose();
				System.exit(0);
			}
			
			// We have the directory so lets save it to the settings file.
			log.info("Saving VLC path to settings.");
			Settings.getSettings().addSetting("vlc_path", dlg.getPath());
		}
		
		// Loading VLC properties.
		Properties p = System.getProperties();
		p.put("jna.library.path", Settings.getSettings().getSetting("vlc_path"));
		
		log.info("VLC settings confirmed!");
		
		log.info("Initializing music player...");
		player = new YStreamPlayer();
		log.info("Music player initialized!");
		
		log.info("Configuring screen...");
		window.setPlayer(player);
		log.info("Screen configured!");
		
		log.info("Client started!");
		
		server = null;
		networkDaemon = null;
		isStreaming = false;
		
		// Now that everything is setup let's start the normal execution
		// of the client.
		while(alive)
		{
			if(noMessages())
				waitForMessage();
			
			Message msg = nextMessage();
			handleMessage(msg);
		}
		
		log.info("Stopping client...");
		
		log.info("Closing GUI...");
		window.setVisible(false);
	}
	
	/**
	 * Gets a reference to the main window.
	 * 
	 * @return main window
	 */
	public YStreamFrame getWindow()
	{
		return window;
	}
	
	/**
	 * Gets the current server connection. This is
	 * null if there is no connection.
	 * 
	 * @return the server reference or null
	 */
	public Server getServerConnection()
	{
		return server;
	}
	
	/**
	 * Checks if the client is currently connected to a server.
	 * 
	 * @return is it streaming?
	 */
	public boolean isStreaming()
	{
		return isStreaming;
	}
	
	/**
	 * Adds a new client listener to the list.
	 * 
	 * @param listener - a client listener
	 */
	public void addListener(YStreamClientListener listener)
	{
		if(listener == null)
			throw new NullPointerException("Must give a valid listener.");
		
		listeners.add(listener);
	}
	
	/**
	 * Gets a reference to the network daemon.
	 * 
	 * @return the network daemon
	 */
	public NetworkDaemon getNetworkDaemon()
	{
		return networkDaemon;
	}

	/* (non-Javadoc)
	 * @see org.durandj.ystream.client.message.MessageHandler#handleMessage(org.durandj.ystream.client.message.Message)
	 */
	@Override
	protected void handleMessage(Message msg)
	{
		switch(msg.getID())
		{
		case CLIENT_RELOAD_PLAYER:
			// We need the player to be stopped.
			while(player.isPlaying() || player.isPaused());
			
			player.queueMessage(new Message(Priority.HIGH, MessageID.KILL, this));
			
			// Wait for the player to die...
			while(player.isAlive());
			
			player = new YStreamPlayer();
			window.setPlayer(player);
			break;
			
		case CONNECT:
		{
			// TODO: The player should probably be stopped.
			
			// We're going to need know the server address and the port so lets check
			// the message contents.
			if(msg.getContents() == null)
				throw new InvalidMessageContentsException("Must give message contents.");
			
			ByteBuffer contents = msg.getContents();
			String address = ByteBufferUtils.getStringFromByteBuffer(contents);
			if(address == null || address.isEmpty())
				throw new InvalidMessageContentsException("Must give the server address.");
			
			int port = contents.getInt();
			try
			{
				networkDaemon = new NetworkDaemon(address, port);
				isStreaming = true;
				server = networkDaemon.getServer();
			}
			catch(UnknownHostException e)
			{
				JOptionPane.showMessageDialog(window,
				                              "The requested server could not be found.",
				                              "Could not find server",
				                              JOptionPane.ERROR_MESSAGE);
				return;
			}
			catch(IOException e)
			{
				JOptionPane.showMessageDialog(window,
				                              "An error occured when connecting to the server.",
				                              "Could not connect to server",
				                              JOptionPane.ERROR_MESSAGE);
				return;
			}
			
			// We need to notify all the listeners that we are now streaming.
			for(int i = listeners.size() - 1; i >= 0; i--)
				listeners.get(i).streamStateChange(true);
			
			break;
		}
		
		case DISCONNECT:
		{
			networkDaemon.queueMessage(new Message(Priority.HIGH,
			                                       MessageID.KILL,
			                                       this));
			
			while(networkDaemon.isAlive())
				new Timer(100);
			
			server = null;
			networkDaemon = null;
			isStreaming = false;
			
			// We need to notify all the listeners that we are no longer streaming.
			for(int i = listeners.size() - 1; i >= 0; i--)
				listeners.get(i).streamStateChange(false);
			break;
		}
		
		case KILL:
			alive = false;
			window.dispose();
			player.queueMessage(msg);
			
			if(networkDaemon != null)
				networkDaemon.queueMessage(msg);
			break;
			
		default:
			log.warning("Unhandled message!\n" +
						 "\tID: " + msg.getID() + "\n" +
						 "\tSender: " + msg.getSender().getClass().getSimpleName() + "\n");
			break;
		}
	}
}
